﻿using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using Loki.Utils;

namespace Loki.Events
{
    /// <summary>
    /// Bridge class for INotifyPropertyChange weakevent mode.
    /// </summary>
    internal class WeakNotifyPropertyChangeBridge
    {
        private INotifyPropertyChanged _Source;
        private ConcurrentDictionary<string, ConcurrentCollection<IWeakCallback>> _PropertyNameToCallbacks;

        /// <summary>
        /// Gets a value indicating whether the event source has active listeners.
        /// </summary>
        /// <value>
        ///     Is <c>true</c> if the event source has active listeners; otherwise, <c>false</c>.
        /// </value>
        public bool HasActiveListeners
        {
            get { return _PropertyNameToCallbacks.Count > 0; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WeakNotifyPropertyChangeBridge"/> class.
        /// </summary>
        /// <param name="P_Source">The source.</param>
        public WeakNotifyPropertyChangeBridge(INotifyPropertyChanged P_Source)
        {
            _Source = P_Source;
            _PropertyNameToCallbacks = new ConcurrentDictionary<string, ConcurrentCollection<IWeakCallback>>();

            P_Source.PropertyChanged += OnPropertyChanged;
        }

        private void OnPropertyChanged(object P_Sender, PropertyChangedEventArgs P_Args)
        {
            var L_CallbacksForProperty = LookupCallbacksForProperty(P_Args.PropertyName);
            if (L_CallbacksForProperty != null)
            {
                foreach (var L_Node in L_CallbacksForProperty)
                {
                    IWeakEventCallback<PropertyChangedEventArgs> L_Callback = (IWeakEventCallback<PropertyChangedEventArgs>)L_Node.Value;

                    if (!L_Callback.Invoke(P_Sender, P_Args))
                    {
                        L_CallbacksForProperty.Remove(L_Node);
                    }
                }
            }
        }

        /// <summary>
        /// Adds the listener.
        /// </summary>
        /// <typeparam name="TListener">The type of the listener.</typeparam>
        /// <param name="P_PropertyName">Name of the property.</param>
        /// <param name="P_Listener">The listener.</param>
        /// <param name="P_PropertyChangedCallback">The property changed callback.</param>
        public void AddListener<TListener>(
            string P_PropertyName,
            TListener P_Listener,
            Action<TListener, object, PropertyChangedEventArgs> P_PropertyChangedCallback)
        {
            var L_CallbacksForProperty = LookupOrCreateCallbacksForProperty(P_PropertyName);
            var L_Callback = new WeakEventCallback<TListener, PropertyChangedEventArgs>(P_Listener, P_PropertyChangedCallback);
            L_CallbacksForProperty.Add(L_Callback);
        }

        /// <summary>
        /// Removes the listener.
        /// </summary>
        /// <param name="P_Listener">The listener.</param>
        /// <param name="P_PropertyName">Name of the property.</param>
        public void RemoveListener(object P_Listener, string P_PropertyName)
        {
            var L_CallbacksForProperty = LookupCallbacksForProperty(P_PropertyName);
            if (L_CallbacksForProperty != null)
            {
                foreach (var L_Node in L_CallbacksForProperty)
                {
                    IWeakEventCallback<PropertyChangedEventArgs> L_Callback = (IWeakEventCallback<PropertyChangedEventArgs>)L_Node.Value;
                    object L_ListenerForCallback = L_Callback.Listener;

                    if (L_ListenerForCallback == null)
                    {
                        L_CallbacksForProperty.Remove(L_Node);
                    }
                    else if (L_ListenerForCallback == P_Listener)
                    {
                        L_CallbacksForProperty.Remove(L_Node);
                        break;
                    }
                }
            }

            CheckForUnsubscribe(LookupCallbacksForProperty(P_PropertyName), P_PropertyName);
        }

        private void CheckForUnsubscribe(ConcurrentCollection<IWeakCallback> P_CallbacksForProperty, string P_PropertyName)
        {
            if (P_CallbacksForProperty.IsEmpty)
            {
                ConcurrentCollection<IWeakCallback> L_OldValue = null;
                _PropertyNameToCallbacks.TryRemove(P_PropertyName, out L_OldValue);
            }

            UnsubscribeIfNoMoreListeners();
        }

        private void UnsubscribeIfNoMoreListeners()
        {
            if (!this.HasActiveListeners)
            {
                LokiServices.Event.UnregisterPropertyChangedSource(_Source);
                _Source.PropertyChanged -= OnPropertyChanged;
            }
        }

        private ConcurrentCollection<IWeakCallback> LookupOrCreateCallbacksForProperty(string P_PropertyName)
        {
            var L_CallbacksForProperty = LookupCallbacksForProperty(P_PropertyName);

            if (L_CallbacksForProperty == null)
            {
                L_CallbacksForProperty = new ConcurrentCollection<IWeakCallback>();
                _PropertyNameToCallbacks.TryAdd(P_PropertyName, L_CallbacksForProperty);
            }

            return L_CallbacksForProperty;
        }

        private ConcurrentCollection<IWeakCallback> LookupCallbacksForProperty(string P_PropertyName)
        {
            ConcurrentCollection<IWeakCallback> L_CallbacksForProperty = null;

            if (_PropertyNameToCallbacks.ContainsKey(P_PropertyName))
            {
                _PropertyNameToCallbacks.TryGetValue(P_PropertyName, out L_CallbacksForProperty);
            }

            return L_CallbacksForProperty;
        }
    }
}